package zdream.pmfield.test.stage.effect;

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.atomic.AtomicInteger;

import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.data.foe.Type;
import zdream.pmfield.data.foe.VStat;
import zdream.pmfield.stage.StageBuilderForTest;
import zdream.pmfield.stage.StageWrapper;
import zdream.pmfield.stage.director.Roll;
import zdream.pmfield.stage.message.ActionRequestMessage;
import zdream.pmfield.stage.message.FoeAddBuffMessage;
import zdream.pmfield.stage.message.FoeReceiveDamageMessage;
import zdream.pmfield.stage.message.FoeRemoveBuffMessage;
import zdream.pmfield.stage.message.RollReportMessage;
import zdream.pmfield.stage.message.TurnFinishedMessage;
import zdream.pmfield.stage.message.TurnStartMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.test.utils.ResourceConstant;
import zdream.pmfield.test.utils.StageConstant;

import static org.junit.Assert.*;

/**
 * @author Zdream
 * @date 2023-12-06
 * @since 0.0.1
 */
public class PoisonedBuffCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.load();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testPoisonedEffect() {
		MoveTemplate poisonMove = storage.findMoveById(10000025, 1);
		assertEquals("肯定中毒", poisonMove.name);
		MoveTemplate physicalMove = storage.findMoveById(10000020, 1);
		assertEquals("物理的", physicalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(poisonMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(physicalMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		System.out.println("TEST 1: apply poisoned");
		AtomicInteger checked = new AtomicInteger(0);

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);

		wrapper.clearOnMessageHooks();
		wrapper.onActionRequestForAll(wrapper.createDefaultController().withActionChecker(action -> {
			assertEquals(poisonMove, action.move.move);
			assertEquals(0, checked.getAndIncrement());
		}));
		wrapper.onMessage(FoeAddBuffMessage.class, message -> {
			assertTrue(message.buff.is(StageConstant.EFFECT_POISONED));
			assertTrue(message.success);
			assertEquals(1, checked.getAndIncrement());
		});
		wrapper.onMessage(TurnFinishedMessage.class, message -> {
			assertEquals(2, checked.getAndIncrement());
		});

		wrapper.runUntil(stage0 -> checked.get() == 3);
		assertEquals(3, checked.get());

		System.out.println("TEST 2: 中毒将减少判定数值");
		checked.set(0);

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.onMessage(FoeRemoveBuffMessage.class, message -> {
			throw new RuntimeException("照理说中毒是不会自动恢复的");
		});
		wrapper.onMessage(FoeReceiveDamageMessage.class, message -> {
			assertEquals("照理说中毒是不会掉血的", foe1, message.foe.foe);
		});
		wrapper.onMessage(RollReportMessage.class, message -> {
			if (message.foe.foe != foe2) {
				return;
			}

			// 减少的判定数值将直接作用在 FoeActor.vStats 中, 导致 FoeActor.vStat < Foe.vStat
			for (Roll roll : message.rolls) {
				assertEquals(roll.baseValue, roll.fixedBaseValue);
			}

			final FoeActor foeActor = message.foe;
			for (int i = 0; i < foeActor.vStats.length; i++) {
				short currentValue = foeActor.vStats[i];
				short baseValue = foe2.vStats[i];
				boolean isLuck = i == VStat.LUCK.ordinal();

				if (isLuck) {
					assertEquals(baseValue, currentValue);
				} else {
					assertEquals(baseValue - 10, currentValue);
				}
			}

			checked.getAndIncrement();
		});

		wrapper.runUntil(stage0 -> checked.get() > 10);

		System.out.println("TEST 3: 中毒是不会自动恢复的");
		wrapper.runUntil(stage -> wrapper.now() > 10);
	}

	@Test
	public void testSteelTypeBlockPoisonedBuff() {
		MoveTemplate poisonMove = storage.findMoveById(10000025, 1);
		assertEquals("肯定中毒", poisonMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000010, 1);
		assertEquals("肯定成功", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(20)
				.withMove(poisonMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(81))
				.withLevel(20)
				.withMove(normalMove)
				.build();
		assertEquals(Type.STEEL, foe2.template.types[1]);
		StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.disableDistanceCheck()
				.withStorage(storage)
				.buildWrapper();

		wrapper.printBeforeHandleMessage();
		AtomicInteger checked = new AtomicInteger();

		wrapper.onDefaultActionRequest();
		wrapper.onMessage(FoeAddBuffMessage.class, message -> {
			// 烧伤效果对火系无效
			assertTrue(message.buff.is(StageConstant.BUFF_POTION));
			assertFalse(message.success);
			assertNotNull(message.rejectBy);
			assertTrue(message.rejectBy.is(StageConstant.BUFF_TYPE));
			assertTrue(message.rejectBy.is(Type.STEEL.toString()));
			checked.incrementAndGet();
		});
		wrapper.runUntil(s -> checked.get() > 0);
		assertTrue(checked.get() > 0);
	}

	@Test
	public void testRemovePoisonedEffect() {
		MoveTemplate poisonMove = storage.findMoveById(10000025, 1);
		assertEquals("肯定中毒", poisonMove.name);
		MoveTemplate recoverMove = storage.findMoveById(10000028, 1);
		assertEquals("对方中毒恢复", recoverMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(poisonMove)
				.withMove(recoverMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		short[] foe2VStats = new short[foe2.vStats.length];
		System.arraycopy(foe2.vStats, 0, foe2VStats, 0, foe2VStats.length);

		System.out.println("TEST 1: apply poisoned");

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.runUntil(FoeAddBuffMessage.class, message -> {
			assertTrue(message.buff.is(StageConstant.EFFECT_POISONED));
			assertTrue(message.success);
			return true;
		});

		final FoeActor foe2Actor = wrapper.stage.findFoe(foe2);
		for (VStat vStat : VStat.values()) {
			assertEquals(foe2.vStats[vStat.ordinal()], foe2VStats[vStat.ordinal()]);

			if (vStat == VStat.LUCK) {
				assertEquals(foe2.vStats[vStat.ordinal()], foe2Actor.vStats[vStat.ordinal()]);
			} else {
				assertEquals(foe2.vStats[vStat.ordinal()] - 10, foe2Actor.vStats[vStat.ordinal()]);
			}
		}

		System.out.println("TEST 2: remove poisoned");

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest(foe2);
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.runUntil(ActionRequestMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);

			final Result commit = message.createReplyTemplate()
					.withMove(recoverMove)
					.withTarget(foe2)
					.commit();

			assertTrue(commit.errorMessage, commit.success);
			return true;
		});
		wrapper.runUntil(FoeRemoveBuffMessage.class, message -> {
			assertTrue(message.foe.foe == foe2);
			assertTrue(message.buff.is(StageConstant.EFFECT_POISONED));
			return true;
		});

		for (VStat vStat : VStat.values()) {
			assertEquals(foe2.vStats[vStat.ordinal()], foe2VStats[vStat.ordinal()]);
			assertEquals(foe2.vStats[vStat.ordinal()], foe2Actor.vStats[vStat.ordinal()]);
		}
	}

	@Test
	public void testStartStageWithPoisonedAbility() {
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(normalMove)
				.withAbilityScripts("apply-buff/self/poisoned")
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();
		wrapper.printBeforeHandleMessage();
		wrapper.runUntil(TurnStartMessage.class);
		assertTrue(wrapper.stage.findFoe(foe1).hasBuff(StageConstant.BUFF_POTION));
	}
}
